<?php
/**
 * FW_i18n
 *
 * 국제화를 지원하는 메세징 처리 객체
 * Gettext 를 기반으로 합니다.
 *
 * @link http://kr1.php.net/manual/en/book.gettext.php
 * @link http://www.gnu.org/software/gettext/gettext.html
 * @author Platform Team
 */

class FW_i18n
{
    /**
     * 인스턴스
     * @var FW_i18n
     */
    private static $instance;

    /**
     * 버젼 - 전체 캐시 갱신을 위하여
     * 해당 객체의 구조가 변경되거나 코드가 변경될 때는 버젼을 변경시켜주도록 하자.
     * @var string
     */
    private $sVersion = '1';

    /**
     * MO 파일 리스트
     * @var array(
     *     '도메인명' => 파일 절대 경로,
     *     ...
     * )
     */
    private $aMoFile;

    /**
     * 캐시 루트 디렉토리 경로
     * @var string
     */
    private $sCacheRoot;
    private $sCacheRootJs;
    private $sCacheRootPhp;

    /**
     * 기본 바인딩 도메인명
     * @var string
     */
    private $sBindDomain;

    /**
     * 원본 도메인 루트 디렉토리 - MO파일 위치한
     * @var string
     */
    private $sDirOrigDomain;

    /**
     * 원본 메세징 루트 디렉토리 - MO파일 위치한
     * @var string
     */
    private $sDirOrigMessages;

    /**
     * MO 파일 자동 갱신 여부
     * @var boolean
     */
    private $isAutoRefresh;

    private function __construct() {}

    /**
     * 인스턴스 생성
     *
     * @return FW_i18n
     */
    public static function getInstance()
    {
        if (!self::$instance) self::$instance = new self;

        return self::$instance;
    }

    /**
     * 인스턴스 반환
     *
     * @return FW_i18n
     */
    public static function instance()
    {
        return self::$instance;
    }

    /**
     * 인스턴스 삭제
     */
    public static function instancedel()
    {
        self::$instance = null;
    }

    /**
     * 초기화
     *
     * @param array $aConfig
     * array(
     *  'enabled'           => 활성화 여부
     *  'locale'            => 언어_지역코드
     *  'locale_category'   => 유효 카테고리
     *  'directory'         => 메세징 디렉토리 경로
     *  'domain'            => 도메인 배열
     *  'bind_domain'       => 최초 바인딩 도메인명
     *  'cache_root'        => 캐시 루트 디렉토리 경로
     *  'auto_refresh'      => MO 파일 자동 갱신 여부
     *  'optimizer'         => OPTIMIZER 객체
     * )
     */
    public function initialize($aConfig)
    {
        if ($aConfig['enabled'] !== true) return;

        $this->_initRequired($aConfig);

        $this->isAutoRefresh = $aConfig['auto_refresh'] ? true : false;
        $this->sCacheRoot = $aConfig['cache_root'];
        $this->sCacheRootJs = $aConfig['cache_root'] . DS . 'js';
        $this->sCacheRootPhp = $aConfig['cache_root'] . DS . 'php';
        $this->sDirOrigDomain = $aConfig['directory'];
        $this->sDirOrigMessages = $aConfig['directory'] . DS . $aConfig['locale'] . DS . 'LC_MESSAGES';

        $this->_createCacheDir();

        $this->_initMoList($aConfig);
        $this->_initBackend($aConfig);
        $this->_initFrontend($aConfig);
    }

    /**
     * 필수 설정값 체크
     */
    private function _initRequired($aConfig)
    {
        if (empty($aConfig['locale']) === true) {
            throw new Exception('[i18n] "locale" is not defined.', E_ERROR);
        }

        if (empty($aConfig['directory']) === true) {
            throw new Exception('[i18n] "directory" is not defined.', E_ERROR);
        }

        if (empty($aConfig['domain']) === true) {
            throw new Exception('[i18n] "domain" is not defined.', E_ERROR);
        }

        if (empty($aConfig['bind_domain']) === true) {
            throw new Exception('[i18n] "bind_domain" is not defined.', E_ERROR);
        }

        if (empty($aConfig['cache_root']) === true) {
            throw new Exception('[i18n] "cache_root" is not defined.', E_ERROR);
        }
    }

    /**
     * MO 파일 리스트 설정
     */
    private function _initMoList($aConfig)
    {
        $this->aMoFile = array();

        if (is_array($aConfig['domain']) === false) {
            return;
        }

        foreach ($aConfig['domain'] as $v) {
            $sPath = $this->sDirOrigMessages . DS . $v . '.mo';

            if (is_file($sPath) === false) {
                continue;
            }

            $this->aMoFile[$v] = $sPath;
        }
    }

    /**
     * 백엔드 설정 - PHP
     */
    private function _initBackend($aConfig)
    {
        putenv('LANG=' . $aConfig['locale']);
        putenv('LANGUAGE=' . $aConfig['locale']);
        setlocale($aConfig['locale_category'], $aConfig['locale']);

        if (empty($this->aMoFile) === true) {
            return;
        }

        $sDomainDir = $this->sDirOrigDomain;

        if ($this->isAutoRefresh === true) {
            $sDomainDir = $this->_getCachePhpDir();

            $sCopyDir = $sDomainDir . DS . $aConfig['locale'] . DS . 'LC_MESSAGES';

            if (is_dir($sCopyDir) === false) {
                mkdir($sCopyDir, 0777, true);

                foreach ($this->aMoFile as $d => $sPath) {
                    copy($sPath, $sCopyDir . DS . $d . '.mo');
                }
            }
        }

        foreach ($this->aMoFile as $d => $sPath) {
            bindtextdomain($d, $sDomainDir);
            bind_textdomain_codeset($d, FW_CHARSET);
        }

        textdomain($aConfig['bind_domain']);
    }

    /**
     * 프론트엔드 설정 - Javascript
     */
    private function _initFrontend($aConfig)
    {
        if (empty($aConfig['optimizer']) === true) {
            return;
        }

        $aConfig['optimizer']->addJS(FW_DIR_FRAMEWORK_RESOURCE . '/js', 'i18n', '__pre_compile__');

        if (empty($this->aMoFile) === true) {
            return;
        }

        $sCacheName = $this->_getCacheJsName($aConfig);
        $sCacheFullPath = $this->sCacheRootJs . DS . $sCacheName;

        if (is_file($sCacheFullPath) === true) {
            $aConfig['optimizer']->addJS($this->sCacheRootJs, $sCacheName, '__pre_compile__', false);
            return;
        }

        require_once 'File/Gettext.php';

        $aTranslate = array();
        $sSource = '';
        foreach ($this->aMoFile as $k => $v) {
            $G = File_Gettext::factory('MO', $v);
            $G->load();

            $aStr = $G->toArray();

            if (empty($aStr['strings']) === false) {
                $aTranslate[$k] = $aStr['strings'];
            }

            if (isset($aStr['meta']['Plural-Forms']) === true) {
                if (preg_match('@;[\s]*plural[\s]*=[\s]*(.*?);@i', $aStr['meta']['Plural-Forms'], $aMatch) !== 0) {
                    $sSource .= '__i18n_plurals_exp_bind__("' . $k . '", function(n) { var __n__ = (' . $aMatch[1] . '); if (typeof __n__ == "boolean") { return __n__ ? 1 : 0; } else { return __n__; } });';
                }
            }
        }

        $sJsonTrans = $this->_jsonEncode($aTranslate);

        $sSource .= '__i18n_regist__(' . $sJsonTrans . ');';
        $sSource .= '__i18n_bind__("' . $aConfig['bind_domain'] . '");';

        if (file_put_contents($sCacheFullPath, $sSource) === false) {
            throw new Exception('[i18n] FrontEnd Cache File is not created.', E_ERROR);
        }

        $aConfig['optimizer']->addJS($this->sCacheRootJs, $sCacheName, '__pre_compile__', false);
    }

    /**
     * JSON 인코딩
     */
    private function _jsonEncode($aData, $iOption=0)
    {
        $aData = iconvDeep($aData, FW_CHARSET, 'utf-8');

        return json_encode($aData, $iOption);
    }

    /**
     * JSON 디코딩
     */
    private function _jsonDecode($sData, $isAssoc=false, $iDepth=512)
    {
        $sData = iconv(FW_CHARSET, 'utf-8//IGNORE//TRANSLIT', $sData);

        return json_decode($sData, $isAssoc, $iDepth);
    }

    /**
     * 캐시 디렉토리 생성
     */
    private function _createCacheDir()
    {
        if (is_dir($this->sCacheRoot) === false) {
            mkdir($this->sCacheRoot, 0777, true);
        }

        if (is_dir($this->sCacheRootJs) === false) {
            mkdir($this->sCacheRootJs, 0777, true);
        }

        if (is_dir($this->sCacheRootPhp) === false) {
            mkdir($this->sCacheRootPhp, 0777, true);
        }
    }

    /**
     * JS 캐시파일명 반환
     */
    private function _getCacheJsName($aConfig)
    {
        $iLast = $this->_getLastMtime();

        $aPath = $this->aMoFile;

        $sFilename = implode(',', $aPath);

        return sha1($sFilename.$aConfig['bind_domain'].$this->sVersion) . '_' . $iLast;
    }

    /**
     * PHP 캐시 디렉토리 반환
     */
    private function _getCachePhpDir()
    {
        $iLast = $this->_getLastMtime();

        $sPath = $this->sCacheRootPhp . DS . $this->sVersion . DS . $iLast;

        return $sPath;
    }

    /**
     * 마지막 수정시간 반환
     */
    private function _getLastMtime()
    {
        $iLast = 0;
        $aPath = $this->aMoFile;

        foreach ($aPath as $f) {
            $mtime = filemtime($f);
            if($iLast < $mtime) $iLast = $mtime;
        }

        return $iLast;
    }
}